/*
 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
 * Copyright (C) 2013 Armink <armink.ztl@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * File: $Id: mbfunccoils_m.c,v 1.60 2013/10/12 15:10:12 Armink Add Master Functions
 */

/* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mb_m.h"
#include "mbconfig.h"
#include "mbframe.h"
#include "mbproto.h"

/* ----------------------- Defines ------------------------------------------*/
//
#define MB_PDU_REQ_READ_ADDR_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_REQ_READ_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_READ_SIZE (4)
#define MB_PDU_FUNC_READ_COILCNT_OFF (MB_PDU_DATA_OFF + 0)
#define MB_PDU_FUNC_READ_VALUES_OFF (MB_PDU_DATA_OFF + 1)
#define MB_PDU_FUNC_READ_SIZE_MIN (1)

#define MB_PDU_REQ_WRITE_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_REQ_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_WRITE_SIZE (4)
#define MB_PDU_FUNC_WRITE_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_FUNC_WRITE_VALUE_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_SIZE (4)

#define MB_PDU_REQ_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_REQ_WRITE_MUL_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF (MB_PDU_DATA_OFF + 4)
#define MB_PDU_REQ_WRITE_MUL_VALUES_OFF (MB_PDU_DATA_OFF + 5)
#define MB_PDU_REQ_WRITE_MUL_SIZE_MIN (5)
#define MB_PDU_REQ_WRITE_MUL_COILCNT_MAX (0x07B0) // 请求写多个线圈的最大数量 1968  实际最大2000(0x07D0)
#define MB_PDU_FUNC_WRITE_MUL_ADDR_OFF (MB_PDU_DATA_OFF)
#define MB_PDU_FUNC_WRITE_MUL_COILCNT_OFF (MB_PDU_DATA_OFF + 2)
#define MB_PDU_FUNC_WRITE_MUL_SIZE (5)

/* ----------------------- Static functions ---------------------------------*/
eMBException prveMBError2Exception(eMBErrorCode eErrorCode);

/* ----------------------- Start implementation -----------------------------*/
#if MB_MASTER_RTU_ENABLED > 0 || MB_MASTER_ASCII_ENABLED > 0

#if MB_FUNC_READ_COILS_ENABLED > 0

/**
 * This function will request read coil.  MB_FUNC_READ_COILS 0x01  可以读取多个
 *
 * @param ucSndAddr salve address
 * @param usCoilAddr coil start address
 * @param usNCoils coil total number
 * @param lTimeOut timeout (-1 will waiting forever)
 *
 * @return error code
 */
eMBMasterReqErrCode eMBMasterReqReadCoils(UCHAR ucSndAddr, USHORT usCoilAddr, USHORT usNCoils, LONG lTimeOut)
{
    UCHAR* ucMBFrame;
    eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR;

    if (ucSndAddr > MB_MASTER_TOTAL_SLAVE_NUM)
        eErrStatus = MB_MRE_ILL_ARG;
    else if (xMBMasterRunResTake(lTimeOut) == FALSE)
        eErrStatus = MB_MRE_MASTER_BUSY;
    else {
        vMBMasterGetPDUSndBuf(&ucMBFrame); // ucMBFrame指向了PDU的第一个地址  （实际是整个modbus帧的第二个字节）
        vMBMasterSetDestAddress(ucSndAddr); // ucSndAddr 实际是整个modbus帧的第一个字节
        ucMBFrame[MB_PDU_FUNC_OFF] = MB_FUNC_READ_COILS; // PDU的第一个字节(功能码)
        ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF] = usCoilAddr >> 8;
        ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF + 1] = usCoilAddr;
        ucMBFrame[MB_PDU_REQ_READ_COILCNT_OFF] = usNCoils >> 8;
        ucMBFrame[MB_PDU_REQ_READ_COILCNT_OFF + 1] = usNCoils;
        vMBMasterSetPDUSndLength(MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE); // 去除从站地址和CRC校验
        (void)xMBMasterPortEventPost(EV_MASTER_FRAME_SENT); // 发送开火信号
        eErrStatus = eMBMasterWaitRequestFinish(); // 等待开火效果反馈
    }
    return eErrStatus;
}

eMBException eMBMasterFuncReadCoils(UCHAR* pucFrame, USHORT* usLen) // 这个函数是干嘛的应该是解析从机发过来的数据的(上面是发给从机,让从机进行读取)
{ // 这个函数是解析从机数据的     这里的usLen没怎么用
    UCHAR* ucMBFrame; // 指向发送数据缓冲区
    USHORT usRegAddress;
    USHORT usCoilCount;
    UCHAR ucByteCount;

    eMBException eStatus = MB_EX_NONE;
    eMBErrorCode eRegStatus; // 表示寄存器的状态

    /* If this request is broadcast, and it's read mode. This request don't need execute. */
    if (xMBMasterRequestIsBroadcast()) { // 广播仅限于写
        eStatus = MB_EX_NONE;

        // 功能码+具体的数据
    } else if (*usLen >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN) { // 这里的usLen是接收到的modbus数据帧中的PDU
        vMBMasterGetPDUSndBuf(&ucMBFrame); // 指向整个modbus发送帧的第二个字节(注意是发送帧)
        usRegAddress = (USHORT)(ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF] << 8); // 高8位
        usRegAddress |= (USHORT)(ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF + 1]); // 低8位
        usRegAddress++; // 这里加1是干啥的？？？

        usCoilCount = (USHORT)(ucMBFrame[MB_PDU_REQ_READ_COILCNT_OFF] << 8); // 这里是发送缓冲区
        usCoilCount |= (USHORT)(ucMBFrame[MB_PDU_REQ_READ_COILCNT_OFF + 1]); // CoilCount 一字节有8bit,可以表示8个线圈
        // 上面是我们要读取的线圈的数量(发送时写入的)
        /* Test if the quantity of coils is a multiple of 8. If not last   //multiple of 8 : 是否是8的倍数
         * byte is only partially field with unused coils set to zero. */
        if ((usCoilCount & 0x0007) != 0) { // 111=0x07  相当于(usCoilCount % 8) != 0
            ucByteCount = (UCHAR)(usCoilCount / 8 + 1); // 最后一个字节会有剩余空间
        } else {
            // 这表示线圈数量是8的个数
            ucByteCount = (UCHAR)(usCoilCount / 8);
        } // 一个线圈一bit,计算有多少个字节
        //(usCoilCount-1) / 8 + 1;   //这里会产生一个问题,就是usCoilCount得>0
        /* Check if the number of registers to read is valid. If not
         * return Modbus illegal data value exception.
         */
        if ((usCoilCount >= 1) && (ucByteCount == pucFrame[MB_PDU_FUNC_READ_COILCNT_OFF])) // 数据域字节数
        { // usCoilCount要读取的线圈个数大于等于1    要读取的字节数和实际读取的字节数相同
          //  pucFrame[MB_PDU_FUNC_READ_COILCNT_OFF]是数据域字节数(从机返回的)     ucByteCount(是主站要读取的,其实是间接计算出来的)
            /* Make callback to fill the buffer.  pucFrame[MB_PDU_FUNC_READ_VALUES_OFF]:实打实的数据来了  usRegAddress:寄存器的起始地址   usCoilCount:寄存器的数量 */
            eRegStatus = eMBMasterRegCoilsCB(&pucFrame[MB_PDU_FUNC_READ_VALUES_OFF], usRegAddress, usCoilCount, MB_REG_READ); // 这里的REG_READ是没有意义的 实际会变成MB_REG_WRITE
            // 这里的MB_REG_READ表示向pucFrame指向的内存区域填写数据  现在pucFrame指向了返回的数据的数据区域开头  //MB_REG_WRITE表示的方向相反
            /* If an error occured convert it into a Modbus exception. */
            if (eRegStatus != MB_ENOERR) { // 这里表示有错误
                eStatus = prveMBError2Exception(eRegStatus); // 错误转换异常
            } //
        } else { // 这里表示返回数据数量不够或者要求的数据量和返回的数据量对不上
            eStatus = MB_EX_ILLEGAL_DATA_VALUE;
        }
    } else {
        /* Can't be a valid read coil register request because the length
         * is incorrect. */
        eStatus = MB_EX_ILLEGAL_DATA_VALUE;
    }
    return eStatus;
}
#endif

#if MB_FUNC_WRITE_COIL_ENABLED > 0

/**
 * This function will request write one coil.   MB_FUNC_WRITE_SINGLE_COIL 0x05
 *
 * @param ucSndAddr salve address
 * @param usCoilAddr coil start address
 * @param usCoilData data to be written  0xFF00表示on 0x0000表示off
 * @param lTimeOut timeout (-1 will waiting forever)
 *
 * @return error code
 *
 * @see eMBMasterReqWriteMultipleCoils
 */
eMBMasterReqErrCode eMBMasterReqWriteCoil(UCHAR ucSndAddr, USHORT usCoilAddr, USHORT usCoilData, LONG lTimeOut)
{ // 如果要设置150地址的线圈,则起始地址为149
    UCHAR* ucMBFrame;
    eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR;

    if (ucSndAddr > MB_MASTER_TOTAL_SLAVE_NUM) { // 1-16
        eErrStatus = MB_MRE_ILL_ARG;
    } else if ((usCoilData != 0xFF00) && (usCoilData != 0x0000)) {
        eErrStatus = MB_MRE_ILL_ARG; // 两个都不等于
    } else if (xMBMasterRunResTake(lTimeOut) == FALSE) { // 表示超时了
        eErrStatus = MB_MRE_MASTER_BUSY;
    } else { // 这里操纵的是PDU  上面几个else都没有时才会执行到这里    ucMBFrame指向了ucMasterRTUSndBuf
        vMBMasterGetPDUSndBuf(&ucMBFrame); // ucMBFrame现在指向了功能码 [从站地址  功能码  0-252字节的数据   CRC16    其中PDU不包含: 从站地址和CRC16 ]
        vMBMasterSetDestAddress(ucSndAddr); // 设置从机地址
        ucMBFrame[MB_PDU_FUNC_OFF] = MB_FUNC_WRITE_SINGLE_COIL; // 写单个线圈
        ucMBFrame[MB_PDU_REQ_WRITE_ADDR_OFF] = usCoilAddr >> 8;
        ucMBFrame[MB_PDU_REQ_WRITE_ADDR_OFF + 1] = usCoilAddr;
        ucMBFrame[MB_PDU_REQ_WRITE_VALUE_OFF] = usCoilData >> 8;
        ucMBFrame[MB_PDU_REQ_WRITE_VALUE_OFF + 1] = usCoilData;
        vMBMasterSetPDUSndLength(MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_SIZE); // modbus PDU应该是不包含从机地址的  PDU有效载荷    modbus报文:  从站地址+有效载荷+CRC16
        (void)xMBMasterPortEventPost(EV_MASTER_FRAME_SENT); // 发送事件,目的是让其开始发送
        eErrStatus = eMBMasterWaitRequestFinish(); // 接收事件,目的是等待发送完成
    }
    return eErrStatus;
}

eMBException eMBMasterFuncWriteCoil(UCHAR* pucFrame, USHORT* usLen)
{ // 这里的数据和长度可以是modbus发送帧的也可以是modbus接收帧的
    USHORT usRegAddress;
    UCHAR ucBuf[2];

    eMBException eStatus = MB_EX_NONE;
    eMBErrorCode eRegStatus;

    if (*usLen == (MB_PDU_FUNC_WRITE_SIZE + MB_PDU_SIZE_MIN)) {
        // 除去 站地址和CRC校验
        usRegAddress = (USHORT)(pucFrame[MB_PDU_FUNC_WRITE_ADDR_OFF] << 8);
        usRegAddress |= (USHORT)(pucFrame[MB_PDU_FUNC_WRITE_ADDR_OFF + 1]);
        usRegAddress++;

        // 这里表示低字节--------------------------------------------------------------这里表示低字节
        if ((pucFrame[MB_PDU_FUNC_WRITE_VALUE_OFF + 1] == 0x00) && ((pucFrame[MB_PDU_FUNC_WRITE_VALUE_OFF] == 0xFF) || (pucFrame[MB_PDU_FUNC_WRITE_VALUE_OFF] == 0x00))) {
            // 0xFF00表示ON 0x0000表示OFF

            ucBuf[1] = 0; // 数据只能是 0x0000 或者 0xFF00

            if (pucFrame[MB_PDU_FUNC_WRITE_VALUE_OFF] == 0xFF) {
                ucBuf[0] = 1; // 表示0x5功能吗写入的是ON
            } else {
                ucBuf[0] = 0; // 表示0x5功能吗写入的是OFF
            }
            eRegStatus = eMBMasterRegCoilsCB(&ucBuf[0], usRegAddress, 1, MB_REG_WRITE);

            /* If an error occured convert it into a Modbus exception. */
            if (eRegStatus != MB_ENOERR) {
                eStatus = prveMBError2Exception(eRegStatus);
            }
        } else {
            eStatus = MB_EX_ILLEGAL_DATA_VALUE;
        }
    } else {
        /* Can't be a valid write coil register request because the length
         * is incorrect. */
        eStatus = MB_EX_ILLEGAL_DATA_VALUE;
    }
    return eStatus;
}

#endif

#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED > 0

/**
 * This function will request write multiple coils.  0x0F  写多个线圈
 *
 * @param ucSndAddr salve address
 * @param usCoilAddr coil start address  //这里的地址还得减1
 * @param usNCoils coil total number
 * @param usCoilData data to be written
 * @param lTimeOut timeout (-1 will waiting forever)
 *
 * @return error code
 *
 * @see eMBMasterReqWriteCoil
 */
eMBMasterReqErrCode eMBMasterReqWriteMultipleCoils(UCHAR ucSndAddr, USHORT usCoilAddr, USHORT usNCoils, UCHAR* pucDataBuffer, LONG lTimeOut)
{
    UCHAR* ucMBFrame;
    USHORT usRegIndex = 0;
    UCHAR ucByteCount;
    eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR;

    if (ucSndAddr > MB_MASTER_TOTAL_SLAVE_NUM)
        eErrStatus = MB_MRE_ILL_ARG;
    else if (usNCoils > MB_PDU_REQ_WRITE_MUL_COILCNT_MAX)
        eErrStatus = MB_MRE_ILL_ARG;
    else if (xMBMasterRunResTake(lTimeOut) == FALSE)
        eErrStatus = MB_MRE_MASTER_BUSY;
    else {
        vMBMasterGetPDUSndBuf(&ucMBFrame);
        vMBMasterSetDestAddress(ucSndAddr);
        ucMBFrame[MB_PDU_FUNC_OFF] = MB_FUNC_WRITE_MULTIPLE_COILS;
        ucMBFrame[MB_PDU_REQ_WRITE_MUL_ADDR_OFF] = usCoilAddr >> 8;
        ucMBFrame[MB_PDU_REQ_WRITE_MUL_ADDR_OFF + 1] = usCoilAddr;
        ucMBFrame[MB_PDU_REQ_WRITE_MUL_COILCNT_OFF] = usNCoils >> 8;
        ucMBFrame[MB_PDU_REQ_WRITE_MUL_COILCNT_OFF + 1] = usNCoils;
        if ((usNCoils & 0x0007) != 0) {
            // 这里是线圈数据字节数     这里表示不是8的整数
            ucByteCount = (UCHAR)(usNCoils / 8 + 1);
        } else {
            // 这里表示是8的整数
            ucByteCount = (UCHAR)(usNCoils / 8);
        }
        ucMBFrame[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF] = ucByteCount;
        ucMBFrame += MB_PDU_REQ_WRITE_MUL_VALUES_OFF; // 这里指向变更的数据
        while (ucByteCount > usRegIndex) {
            *ucMBFrame++ = pucDataBuffer[usRegIndex++];
        }
        // 循环会执行ucByteCount次
        vMBMasterSetPDUSndLength(MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_MUL_SIZE_MIN + ucByteCount);
        (void)xMBMasterPortEventPost(EV_MASTER_FRAME_SENT);
        eErrStatus = eMBMasterWaitRequestFinish();
    }
    return eErrStatus;
}

eMBException
eMBMasterFuncWriteMultipleCoils(UCHAR* pucFrame, USHORT* usLen)
{
    USHORT usRegAddress;
    USHORT usCoilCnt;
    UCHAR ucByteCount;
    UCHAR ucByteCountVerify;
    UCHAR* ucMBFrame;

    eMBException eStatus = MB_EX_NONE;
    eMBErrorCode eRegStatus;

    /* If this request is broadcast, the *usLen is not need check. */
    if ((*usLen == MB_PDU_FUNC_WRITE_MUL_SIZE) || xMBMasterRequestIsBroadcast()) {
        vMBMasterGetPDUSndBuf(&ucMBFrame);
        usRegAddress = (USHORT)(pucFrame[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF] << 8);
        usRegAddress |= (USHORT)(pucFrame[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF + 1]);
        usRegAddress++;

        usCoilCnt = (USHORT)(pucFrame[MB_PDU_FUNC_WRITE_MUL_COILCNT_OFF] << 8);
        usCoilCnt |= (USHORT)(pucFrame[MB_PDU_FUNC_WRITE_MUL_COILCNT_OFF + 1]);

        ucByteCount = ucMBFrame[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF];

        /* Compute the number of expected bytes in the request. */
        if ((usCoilCnt & 0x0007) != 0) {
            ucByteCountVerify = (UCHAR)(usCoilCnt / 8 + 1);
        } else {
            ucByteCountVerify = (UCHAR)(usCoilCnt / 8);
        }

        if ((usCoilCnt >= 1) && (ucByteCountVerify == ucByteCount)) {
            eRegStatus = eMBMasterRegCoilsCB(&ucMBFrame[MB_PDU_REQ_WRITE_MUL_VALUES_OFF],
                usRegAddress, usCoilCnt, MB_REG_WRITE);

            /* If an error occured convert it into a Modbus exception. */
            if (eRegStatus != MB_ENOERR) {
                eStatus = prveMBError2Exception(eRegStatus);
            }
        } else {
            eStatus = MB_EX_ILLEGAL_DATA_VALUE;
        }
    } else {
        /* Can't be a valid write coil register request because the length
         * is incorrect. */
        eStatus = MB_EX_ILLEGAL_DATA_VALUE;
    }
    return eStatus;
}

#endif
#endif
